/* Protein.c -- Functions to read both plain and compressed (gzip, deflate) files.
 * Could have used zlib, but this is more fun.
 *
 * Author: Matt Menke (2007)
 *
 * Matt is licensed under the GNU public license version 2.0.
 *
 * If you would like to license Matt in an enviroment where the GNU public license is
 * unacceptable (such as inclusion in a non-GPL software package) comercial Matt
 * licensing is available through the MIT and Tufts offices of Technology Transfer.
 * Contact betawrap@csail.mit.edu or cowen@cs.tufts.edu for more information.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "FileReader.h"
#include "../ThirdParty/zlib/zlib.h"

FileReader *readerOpen(char *name);
void readerClose(FileReader *FileReader);

#define BUFFER_SIZE (1<<14)

struct BUFFER {
	int used;
	int read;
	unsigned char data[BUFFER_SIZE];
};
typedef struct BUFFER Buffer;

struct FILE_READER {
	gzFile gzfile;
	Buffer buffer;
};

FileReader *readerCreateSub(gzFile gzfile) {
	FileReader *fileReader;
	if (!gzfile) return 0;
	fileReader = (FileReader*) calloc(1, sizeof(FileReader));
	if (!fileReader) {
		gzclose(gzfile);
		return 0;
	}
	fileReader->gzfile = gzfile;

	return fileReader;
}

FileReader *readerCreate(int file) {
	if (file == -1) return 0;
	return readerCreateSub(gzdopen(file, "rb"));
}

FileReader *readerOpen(char *name) {
	return readerCreateSub(gzopen(name, "rb"));
}

int readerGetError(FileReader *fileReader) {
	int errnum;
	if (!fileReader) return 0;
	gzerror(fileReader->gzfile, &errnum);
	// 0 on Z_OK.  Otherwise can recover if using gzip headers.
	// Z_OK is defined to be 0, but docs don't seem to guarantee it.
	if (errnum == Z_STREAM_END) errnum = Z_OK;
	return errnum - Z_OK;
}

void readerClose(FileReader *fileReader) {
	if (fileReader) {
		gzclose(fileReader->gzfile);
		free(fileReader);
	}
}

int readerUpdateBuffer(FileReader *fileReader) {
	int read;
	if (fileReader->buffer.used == BUFFER_SIZE) {
		// Only happens if size passed to readerRead is greater than BUFFER_SIZE 
		if (fileReader->buffer.read == 0) {
			return 0;
		}
		memmove(fileReader->buffer.data, fileReader->buffer.data + fileReader->buffer.read, fileReader->buffer.used -= fileReader->buffer.read);
		fileReader->buffer.read = 0;
	}

	read = gzread(fileReader->gzfile, fileReader->buffer.data + fileReader->buffer.used, BUFFER_SIZE-fileReader->buffer.used);
	if (read <= 0) return 0;
	fileReader->buffer.used += read;
	return read;
}

int readerGetChar(FileReader *fileReader) {
	if (fileReader->buffer.used - fileReader->buffer.read > 0) {
		return (unsigned char)fileReader->buffer.data[fileReader->buffer.read++];
	}
	else {
		if (!readerUpdateBuffer(fileReader))
			return EOF;
		return readerGetChar(fileReader);
	}
}

int readerRead(void *out, int size, int count, FileReader *fileReader) {
	int read = 0;
	while (read<count) {
		if (fileReader->buffer.used - fileReader->buffer.read >= size) {
			int copyCount = count;
			int copyBytes = count*size;
			if (copyBytes > fileReader->buffer.used - fileReader->buffer.read) {
				copyCount = (fileReader->buffer.used - fileReader->buffer.read)/size;
				copyBytes = size * copyCount;
			}
			memcpy(out, fileReader->buffer.data + fileReader->buffer.read, copyBytes);
			out = ((char*)out)+copyBytes;
			fileReader->buffer.read += copyBytes;
			read += copyCount;
			count -= copyCount;
		}
		else {
			if (!readerUpdateBuffer(fileReader)) break;
		}
	}
	if (read || size == 0) return read;
	return -1;
}


int readerGetLine(FileReader* fileReader, char **_line, int *_lineLen) {
	int i;

	// While creating local copies of everything is a bit messy,
	// reduces register pressure, as don't have to write back
	// until the end.  Gives ~30% speedup in this function.
	// Not huge, in the scheme of things, but loading large
	// files can be slow, and this function is one of the more
	// time consuming ones involved in that.  ReadBits,
	// gzReadSub, and getHuffmanCode are the other big ones.
	int used = fileReader->buffer.used;
	int read = fileReader->buffer.read;
	unsigned char *data = fileReader->buffer.data;
	unsigned char *line = *_line;
	int lineLen = *_lineLen;

	if (!line) {
		lineLen = 100;
		line = (char*) malloc(sizeof(char)* (2 + lineLen));
	}
	i=0;
	while (1) {
		char c;
		if (used != read) {
			c = data[read++];
			if (c != '\n' && c != '\r') {
				if (i != lineLen) {
					line[i++] = c;
				}
				else {
					lineLen *= 2;
					line = (char*)realloc(line, sizeof(char)* (2+lineLen));
					line[i++] = c;
				}
			}
			else {
				if (i) {
					fileReader->buffer.used = used;
					fileReader->buffer.read = read;
					break;
				}
			}
		}
		else {
			fileReader->buffer.used = used;
			fileReader->buffer.read = read;

			if (!readerUpdateBuffer(fileReader))
				break;

			used = fileReader->buffer.used;
			read = fileReader->buffer.read;
			// Not currently needed.
			data = fileReader->buffer.data;
		}
	}
	line[i]=0;
	_line[0] = line;
	_lineLen[0] = lineLen;
	return i;
}


